package no.nordicsemi.android.nrfmesh.widgets;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import no.nordicsemi.android.nrfmesh.R;
import android.content.Intent;
import android.graphics.Color;

// ==== 新增：用于将图片坐标映射到屏幕坐标 ====
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.ViewTreeObserver;

import no.nordicsemi.android.nrfmesh.MainActivity;
import no.nordicsemi.android.nrfmesh.auth.session.SessionManager;
import no.nordicsemi.android.nrfmesh.auth.ui.AuthActivity;
import no.nordicsemi.android.nrfmesh.auth.ui.account.PersonalInfoDialogFragment;
import no.nordicsemi.android.nrfmesh.auth.ui.account.SwitchAccountDialogFragment;
import dagger.hilt.android.AndroidEntryPoint;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.MediatorLiveData;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import android.os.SystemClock;
import no.nordicsemi.android.mesh.transport.ProvisionedMeshNode;
import no.nordicsemi.android.nrfmesh.viewmodels.SharedViewModel;
import no.nordicsemi.android.nrfmesh.viewmodels.ScannerViewModel;
import no.nordicsemi.android.nrfmesh.viewmodels.ScannerLiveData;
import no.nordicsemi.android.nrfmesh.node.adapter.NodeAdapter;
import no.nordicsemi.android.nrfmesh.ble.adapter.DevicesAdapter;
import no.nordicsemi.android.nrfmesh.adapter.ExtendedBluetoothDevice;
import no.nordicsemi.android.nrfmesh.ble.BleMeshManager;
import no.nordicsemi.android.nrfmesh.autobind.MeshAutoConfigurator;
import android.content.SharedPreferences;
import no.nordicsemi.android.nrfmesh.provisioners.MeshHeadlessProvisioner;
import no.nordicsemi.android.nrfmesh.utils.ProvisionerStates;
import no.nordicsemi.android.mesh.MeshNetwork;
import no.nordicsemi.android.nrfmesh.utils.NodeOnlineHelper;
import no.nordicsemi.android.mesh.transport.Element;
import no.nordicsemi.android.mesh.transport.MeshModel;
import no.nordicsemi.android.mesh.models.VendorModel;
import no.nordicsemi.android.mesh.ApplicationKey;
import no.nordicsemi.android.mesh.transport.MeshMessage;
import no.nordicsemi.android.mesh.transport.VendorModelMessageAcked;

// === 自适应横竖屏相关 ===
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;

// === 新增：BLE 权限与适配器检查所需 import ===
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.provider.Settings;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.os.Build;

// ==== 新增：取到布局里的图片与标记视图 ====
import android.widget.ImageView;

@AndroidEntryPoint
public class UserMainActivity extends AppCompatActivity {

    // UI
    private Button btnPlay, btnBluetoothMesh; // 设备启动键（启动/停止）
    private TextView btnGearMinus, btnGearPlus, tvGearValue;
    private TextView btnTimeMinus, btnTimePlus, tvTimeValue;
    private Spinner spMode, deviceSpinner;

    // ===== 新增：穴位图 & 标记点 & 气泡 =====
    private ImageView ivAcupoints;
    private View markerHans;
    private View bubbleHans;

    // ===== 新增：右臂自上而下第 3 个黑点（归一化坐标，基于原图尺寸 0..1）=====
    // 如需 1~2px 微调：x 往右加，y 往下加
    private static final PointF DOT_RIGHT_ARM_3 = new PointF(0.782f, 0.566f);

    // 状态
    private int gear = 6;              // 与界面的“06”一致
    private final int gearMin = 1, gearMax = 10;

    private int timeMinutes = 5;       // 与界面的“05:00”一致（未计时时显示 MM:00）
    private final int timeMin = 0, timeMax = 60;

    private boolean isPlaying = false; // 设备是否处于“启动”状态
    private int currentMode = 0;       // 0=未选、1=模式一、2=模式二
    // 播放触发的自动下发挂起标记（在配网完成后自动发送）
    private boolean waveDispatchRequested = false;
    private int pendingWaveType = 0;   // 1=向一个节点下发；2=向两个节点下发
    private long waveRetryDeadlineUptime = 0; // 截止时间，避免无限重试
    private static final int WaveRetryMaxMs = 8000; // 最大重试时长
    private static final int WaveRetryIntervalMs = 500; // 重试间隔

    // 计时相关
    private int remainingSeconds = 0;                // 剩余秒数；未启动或已重置时为 0
    private final Handler timerHandler = new Handler(Looper.getMainLooper());
    private final Runnable tick = new Runnable() {
        @Override public void run() {
            if (!isPlaying) return;
            if (remainingSeconds > 0) {
                remainingSeconds--;
                renderTime();
                timerHandler.postDelayed(this, 2000);
            } else {
                // 到 0 自动停止
                isPlaying = false;
                renderPlay();
                toast("倒计时结束");
            }
        }
    };

    private static final String KEY_GEAR   = "state_gear";
    private static final String KEY_TIME   = "state_time";
    private static final String KEY_PLAY   = "state_play";
    private static final String KEY_REMAIN = "state_remain";

    // —— 新增：Mesh 列表与 ViewModel ——
    private SharedViewModel mSharedViewModel;
    private ScannerViewModel mScannerViewModel;
    private MediatorLiveData<List<ProvisionedMeshNode>> mFilteredNodes = new MediatorLiveData<>();
    private NodeAdapter mNodeAdapter;
    private RecyclerView mNodesRecyclerView;
    private TextView mEmptyNodesText;

    private DevicesAdapter mAvailableDevicesAdapter;
    private RecyclerView mAvailableNodesRecyclerView;
    private TextView mEmptyAvailableText;
    // 新增：空文案默认值与无界面配网器
    private CharSequence mEmptyAvailableDefaultText;
    private MeshHeadlessProvisioner headless;
    // 自动配置器
    private MeshAutoConfigurator mMeshAutoConfigurator;
    // —— 模式触发的自动配网队列 ——
    private final java.util.ArrayDeque<ExtendedBluetoothDevice> provisionQueue = new java.util.ArrayDeque<>();
    private boolean autoProvisionActive = false;
    private String currentProvisioningNodeName = null;

    // 设备下拉：动态项
    private final List<String> deviceSpinnerItems = new ArrayList<>();
    private final Map<Integer, ProvisionedMeshNode> spinnerIndexToNodeMap = new HashMap<>();
    private android.widget.ArrayAdapter<String> deviceSpinnerAdapter;
    // —— 新增：首次点击颜色映射 ——
    private final Map<String, Integer> firstClickAssignedColor = new HashMap<>();
    private int nextColorToAssign = 1;

    // 与主界面保持一致：多选与阻塞
    private final List<ProvisionedMeshNode> selectedNodes = new ArrayList<>();
    private final Set<ProvisionedMeshNode> blockedNodes = new HashSet<>();

    // 与主界面保持一致：配置中的节点延迟显示
    private final Map<String, Long> mPendingVisibility = new HashMap<>();
    private final Map<String, Runnable> mPendingRunnables = new HashMap<>();
    private final Handler mDelayHandler = new Handler(Looper.getMainLooper());
    private static final int DelayTime_ms = 150;
    private final Handler mAvailableRefreshHandler = new Handler(Looper.getMainLooper());
    private final Runnable mAvailableRefreshTick = new Runnable() {
        @Override public void run() {
            if (mAvailableDevicesAdapter != null) {
                mAvailableDevicesAdapter.notifyDataSetChanged();
            }
            renderAvailableEmpty();
            mAvailableRefreshHandler.postDelayed(this, 2000);
        }
    };

    // —— 统一的在线识别辅助 ——
    private NodeOnlineHelper onlineHelper;

    // ======== 横竖屏自适应：滞回 + 去抖 ========
    /** 宽度 ≤ 600dp 且当前为竖屏时，切到横屏 */
    private static final int NARROW_TO_LANDSCAPE_DP = 600;
    /** 宽度 ≥ 720dp 且当前为横屏时，切回竖屏（滞回，避免临界抖动） */
    private static final int WIDE_TO_PORTRAIT_DP    = 720;
    /** 两次方向切换之间的最短间隔（毫秒），防止短时间反复触发 */
    private static final long ORIENTATION_DEBOUNCE_MS = 1500L;

    private int lastAppliedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
    private long lastOrientationSwitchTs = 0L;
    // ==========================================

    // ======== 新增：BLE 权限/就绪检查 ========
    private static final int REQ_BLE_PERMS = 0xB1E;

    private boolean hasBleFeature() {
        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }

    private BluetoothAdapter btAdapter() {
        BluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        return bm != null ? bm.getAdapter() : null;
    }

    private boolean hasScanPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            return ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
                    == PackageManager.PERMISSION_GRANTED;
        } else {
            // Android 6–11 需要定位权限
            return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                    == PackageManager.PERMISSION_GRANTED;
        }
    }

    private void requestScanPermissionIfNeeded() {
        if (hasScanPermission()) return;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ActivityCompat.requestPermissions(this,
                    new String[]{ Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT },
                    REQ_BLE_PERMS);
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{ Manifest.permission.ACCESS_FINE_LOCATION },
                    REQ_BLE_PERMS);
        }
    }

    private boolean isLocationEnabledForPreS() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) return true; // S+ 不要求位置总开关
        try {
            int mode = Settings.Secure.getInt(getContentResolver(),
                    Settings.Secure.LOCATION_MODE,
                    Settings.Secure.LOCATION_MODE_OFF);
            return mode != Settings.Secure.LOCATION_MODE_OFF;
        } catch (Throwable ignore) {
            return false;
        }
    }

    /** 返回 true 表示可以安全开扫；false 则已提示并不启动扫描 */
    private boolean ensureBleReady() {
        if (!hasBleFeature()) { toast("此设备不支持 BLE 扫描"); return false; }

        BluetoothAdapter adp = btAdapter();
        if (adp == null) { toast("没有可用的蓝牙适配器"); return false; }
        if (!adp.isEnabled()) { toast("请先开启蓝牙再进行扫描"); return false; }

        if (!hasScanPermission()) {
            requestScanPermissionIfNeeded();
            return false;
        }

        if (!isLocationEnabledForPreS()) {
            toast("请开启系统位置服务以进行 BLE 扫描");
            return false;
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQ_BLE_PERMS) {
            boolean granted = true;
            for (int r : grantResults) granted &= (r == PackageManager.PERMISSION_GRANTED);
            if (granted) {
                if (mScannerViewModel != null && ensureBleReady()) {
                    mScannerViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
                }
            } else {
                toast("未授予蓝牙扫描权限，已停止扫描");
            }
        }
    }
    // ==========================================

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_main); // res/layout/activity_user_main.xml

        // 不在 onCreate 里立即强制设方向，避免初始化阶段抖动
        // 将判断放在 onResume 与 onConfigurationChanged 里做

        // ====== 顶部栏（与医生页位置一致）======
        int tbId = getResources().getIdentifier("toolbar", "id", getPackageName());
        View maybeToolbar = tbId != 0 ? findViewById(tbId) : null;
        if (maybeToolbar instanceof Toolbar) {
            setSupportActionBar((Toolbar) maybeToolbar);
            if (getSupportActionBar() != null) getSupportActionBar().setTitle("");
        }

        // 绑定控件（id 必须与 XML 匹配）
        btnPlay          = findViewById(R.id.btnPlay);
        btnBluetoothMesh = findViewById(R.id.btnBluetoothMesh);

        btnGearMinus = findViewById(R.id.btnGearMinus);
        btnGearPlus  = findViewById(R.id.btnGearPlus);
        tvGearValue  = findViewById(R.id.tvGearValue);

        btnTimeMinus = findViewById(R.id.btnTimeMinus);
        btnTimePlus  = findViewById(R.id.btnTimePlus);
        tvTimeValue  = findViewById(R.id.tvTimeValue);

        spMode   = findViewById(R.id.spMode);
        deviceSpinner = findViewById(R.id.deviceSpinner);

        // ==== 新增：穴位图与标记视图 ====
        ivAcupoints = findViewById(R.id.ivAcupoints);
        markerHans  = findViewById(R.id.markerHans);
        bubbleHans  = findViewById(R.id.bubbleHans);

        // —— 新增：Mesh 面板控件 ——
        mNodesRecyclerView = findViewById(R.id.nodesRecyclerView);
        mEmptyNodesText = findViewById(R.id.emptyNodesText);
        mAvailableNodesRecyclerView = findViewById(R.id.availableNodesRecyclerView);
        mEmptyAvailableText = findViewById(R.id.emptyAvailableText);
        if (mEmptyAvailableText != null) {
            mEmptyAvailableDefaultText = mEmptyAvailableText.getText();
        }

        // 初始化 ViewModel（需要 @AndroidEntryPoint）
        mSharedViewModel = new ViewModelProvider(this).get(SharedViewModel.class);
        mScannerViewModel = new ViewModelProvider(this).get(ScannerViewModel.class);
        // 初始化在线识别辅助（方案B：复用通用方法）
        onlineHelper = new NodeOnlineHelper(
                this,
                mSharedViewModel,
                mDelayHandler,
                () -> headless == null || !headless.isRunning()
        );
        onlineHelper.setOnlineUpdatedCallback(() -> {
            pruneSelectionForOfflineNodes();
            refreshFilteredNodes();
        });
        // 初始化自动配置器，并准备默认 AppKey
        mMeshAutoConfigurator = MeshAutoConfigurator.getInstance();
        try {
            mMeshAutoConfigurator.initDefaultAppKey(mSharedViewModel.getMeshManagerApi());
        } catch (Exception e) {
            Toast.makeText(this, "初始化 AppKey 失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }

        // 配置“已配网节点”列表
        if (mNodesRecyclerView != null) {
            mNodesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
            mNodeAdapter = new NodeAdapter(this, mFilteredNodes);
            mNodesRecyclerView.setAdapter(mNodeAdapter);
            // —— 新增：颜色与点击监听 ——
            mNodeAdapter.setAssignedColors(firstClickAssignedColor);
            mNodeAdapter.setOnItemClickListener(new NodeAdapter.OnItemClickListener() {
                @Override
                public void onConfigureClicked(final ProvisionedMeshNode node) {
                    handleNodeClick(node);
                    // 节点被点击后可能是首次分配颜色 → 同步到标记点
                    updateMarkerColorForSelectedNode(node);
                }
            });
            // 删除图标点击：弹出确认框并删除节点
            mNodeAdapter.setOnDeleteClickListener((position, node) -> {
                new AlertDialog.Builder(UserMainActivity.this)
                        .setIcon(R.drawable.ic_delete)
                        .setTitle(R.string.title_delete_node)
                        .setMessage(R.string.delete_node_rationale)
                        .setNegativeButton(R.string.no, (dialog, which) -> {
                            mNodeAdapter.notifyItemChanged(position);
                        })
                        .setPositiveButton(R.string.yes, (dialog, which) -> {
                            final ProvisionedMeshNode target = mNodeAdapter.getItem(position);
                            if (target == null) {
                                Toast.makeText(UserMainActivity.this, "节点不存在或已被删除", Toast.LENGTH_SHORT).show();
                                mNodeAdapter.notifyItemChanged(position);
                                return;
                            }
                            final boolean deleted = mSharedViewModel.getMeshManagerApi().getMeshNetwork().deleteNode(target);
                            if (deleted) {
                                final ProvisionedMeshNode selected = mSharedViewModel.getSelectedMeshNode().getValue();
                                if (selected != null && selected.getUnicastAddress() == target.getUnicastAddress()) {
                                    mSharedViewModel.clearSelectedMeshNode();
                                    mNodeAdapter.setSelectedNode(null);
                                }
                                Toast.makeText(UserMainActivity.this, getString(R.string.node_deleted), Toast.LENGTH_SHORT).show();
                                // 新增：节点删除成功后，重新启动扫描以发现被重置的设备
                                if (mScannerViewModel != null) {
                                    mScannerViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
                                }
                            } else {
                                Toast.makeText(UserMainActivity.this, "节点删除失败", Toast.LENGTH_LONG).show();
                                mNodeAdapter.notifyItemChanged(position);
                            }
                        })
                        .show();
            });
        }
        // —— 观察代理连接与地址：转调到通用辅助 ——
        mSharedViewModel.isConnectedToProxy().observe(this, isConnected -> {
            onlineHelper.onProxyConnectionChanged(isConnected);
        });
        mSharedViewModel.getConnectedProxyAddress().observe(this, addr -> {
            onlineHelper.onConnectedProxyAddress(addr);
        });

        // 组合源：网络变化 & 节点列表
        mFilteredNodes.addSource(mSharedViewModel.getNodes(), nodes -> {
            List<ProvisionedMeshNode> filtered = onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms);
            onlineHelper.setFilteredNodesWithOfflineToast(mFilteredNodes, filtered);
            updateDeviceSpinner(filtered);
            renderNodesEmpty(filtered);
        });
        mFilteredNodes.addSource(mSharedViewModel.getNetworkLiveData(), net -> {
            List<ProvisionedMeshNode> nodes = mSharedViewModel.getNodes().getValue();
            List<ProvisionedMeshNode> filtered = onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms);
            onlineHelper.setFilteredNodesWithOfflineToast(mFilteredNodes, filtered);
            updateDeviceSpinner(filtered);
            renderNodesEmpty(filtered);
        });

        // 配置“可用设备（扫描）”列表
        if (mAvailableNodesRecyclerView != null) {
            mAvailableNodesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
            mAvailableDevicesAdapter = new DevicesAdapter(this, mScannerViewModel.getScannerRepository().getScannerResults());
            mAvailableDevicesAdapter.setOnItemClickListener(this::onAvailableDeviceClicked);
            mAvailableNodesRecyclerView.setAdapter(mAvailableDevicesAdapter);
            // 初次渲染空态（避免首次进入时空态未同步）
            renderAvailableEmpty();
        }
        mScannerViewModel.getScannerRepository().getScannerResults().observe(this, results -> {
            renderAvailableEmpty();
        });

        // 设备下拉改用动态数据（仅在无现有适配器时设置，避免覆盖原配置）
        if (deviceSpinner != null) {
            deviceSpinnerAdapter = new ColoredDeviceAdapter(android.R.layout.simple_spinner_item, deviceSpinnerItems);
            deviceSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            deviceSpinner.setAdapter(deviceSpinnerAdapter);
        }

        // 从界面文本兜底解析初始值（避免与 XML 不一致）
        gear = safeParseGear(tvGearValue != null ? tvGearValue.getText().toString() : null, gear);
        timeMinutes = safeParseTimeToMinutes(tvTimeValue != null ? tvTimeValue.getText().toString() : null, timeMinutes);

        // 恢复状态
        if (savedInstanceState != null) {
            gear = savedInstanceState.getInt(KEY_GEAR, gear);
            timeMinutes = savedInstanceState.getInt(KEY_TIME, timeMinutes);
            isPlaying = savedInstanceState.getBoolean(KEY_PLAY, false);
            remainingSeconds = savedInstanceState.getInt(KEY_REMAIN, 0);
        }

        // 首次渲染
        renderGear();
        renderTime();
        renderPlay(); // 渲染设备启动键（▶/⏸）

        // 若恢复时正在计时，继续 tick
        if (isPlaying) {
            timerHandler.removeCallbacks(tick);
            timerHandler.post(tick);
        }

        // —— 点击与交互 ——
        if (btnGearMinus != null) {
            btnGearMinus.setOnClickListener(v -> {
                if (gear > gearMin) gear -= 1;
                renderGear();
            });
        }
        if (btnGearPlus != null) {
            btnGearPlus.setOnClickListener(v -> {
                if (gear < gearMax) gear += 1;
                renderGear();
            });
        }

        if (btnTimeMinus != null) {
            btnTimeMinus.setOnClickListener(v -> {
                if (timeMinutes > timeMin) timeMinutes -= 1;
                if (isPlaying && remainingSeconds > 60) remainingSeconds -= 60;
                renderTime();
            });
        }
        if (btnTimePlus != null) {
            btnTimePlus.setOnClickListener(v -> {
                if (timeMinutes < timeMax) timeMinutes += 1;
                if (isPlaying) remainingSeconds += 60;
                renderTime();
            });
        }

        if (btnPlay != null) {
            btnPlay.setOnClickListener(v -> {
                if (!isPlaying) {
                    isPlaying = true;
                    remainingSeconds = timeMinutes * 60;
                    renderPlay();
                    timerHandler.removeCallbacks(tick);
                    timerHandler.post(tick);
                    toast("设备已启动");

                    // —— 改造：点击启动时自动完成（扫描→配网→选中/配置/分色→下发JSON）
                    if (currentMode == 1 || currentMode == 2 || currentMode == 3) {
                        final int required = currentMode; // 1、2 或 3
                        pendingWaveType = required;
                        waveDispatchRequested = true;
                        waveRetryDeadlineUptime = android.os.SystemClock.uptimeMillis() + WaveRetryMaxMs;
                        pruneSelectionForOfflineNodes();
                        if (selectedNodes.size() >= required) {
                            // 已有足够选中节点，按挂起流程依次下发颜色与波形
                            tryDispatchPendingWave();
                        } else {
                            // 不足则自动从可用设备中开始配网，完成后在回调中自动下发
                            startAutoProvisionForMode(required);
                        }
                    }
                } else {
                    isPlaying = false;
                    renderPlay();
                    timerHandler.removeCallbacks(tick);
                    toast("设备已停止");
                    // 停止时清空挂起状态，避免误下发
                    waveDispatchRequested = false;
                    pendingWaveType = 0;
                    waveRetryDeadlineUptime = 0;
                }
                // TODO: 在这里接入你的“设备启动/停止”业务逻辑
            });
        }

        if (btnBluetoothMesh != null) {
            btnBluetoothMesh.setOnClickListener(v -> onBluetoothMeshPressed());
        }

        if (spMode != null) {
            spMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    Object item = parent.getItemAtPosition(position);
                    String text = item != null ? item.toString() : "";
                    toast("模式已选择：" + text);
                    if (text.contains("模式二") || position == 2) {
                        currentMode = 2;
                    } else if (text.contains("模式一") || position == 1) {
                        currentMode = 1;
                    } else if (text.contains("模式三") || position == 3) {
                        currentMode = 3;
                    } else {
                        currentMode = 0;
                    }
                }
                @Override public void onNothingSelected(AdapterView<?> parent) { }
            });
        }

        if (deviceSpinner != null) {
            deviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    Object item = parent.getItemAtPosition(position);
                    toast("设备已选择：" + (item != null ? item.toString() : ""));
                    ProvisionedMeshNode node = spinnerIndexToNodeMap.get(position);
                    if (node != null) {
                        mSharedViewModel.setSelectedMeshNode(node);
                    }
                    // —— 选择设备后，标记点颜色立即跟随 —— //
                    updateMarkerColorForSelectedNode(node);
                }
                @Override public void onNothingSelected(AdapterView<?> parent) { }
            });
        }

        // ===== 标记点：定位与点击 =====
        if (ivAcupoints != null && markerHans != null) {
            // 布局完成后定位一次
            ivAcupoints.getViewTreeObserver().addOnGlobalLayoutListener(
                    new ViewTreeObserver.OnGlobalLayoutListener() {
                        @Override public void onGlobalLayout() {
                            ivAcupoints.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                            placeMarkerOnImage(ivAcupoints, markerHans, DOT_RIGHT_ARM_3);
                        }
                    });
            // 尺寸变化（旋转/分屏）时重算
            ivAcupoints.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) ->
                    placeMarkerOnImage(ivAcupoints, markerHans, DOT_RIGHT_ARM_3));
            // 点击点：切换气泡
            markerHans.setOnClickListener(v -> {
                if (bubbleHans != null) {
                    bubbleHans.setVisibility(bubbleHans.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
                }
            });
        }
        // 初始颜色：若已有选中节点则跟随，否则保持默认
        updateMarkerColorForSelectedNode(mSharedViewModel.getSelectedMeshNode().getValue());
    }

    // === 配置变化时再次评估（Manifest 已声明 configChanges） ===
    @Override
    public void onConfigurationChanged(@androidx.annotation.NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ensureOrientationWithHysteresis();
        // 方向变化后，重新放置标记点
        if (ivAcupoints != null && markerHans != null) {
            placeMarkerOnImage(ivAcupoints, markerHans, DOT_RIGHT_ARM_3);
        }
    }

    // === 基于“滞回 + 去抖”的横竖屏自适应（平板稳定处理） ===
    private void ensureOrientationWithHysteresis() {
        final long now = System.currentTimeMillis();
        if (now - lastOrientationSwitchTs < ORIENTATION_DEBOUNCE_MS) {
            return; // 去抖：距离上次切换太近，忽略
        }

        final Configuration cfg = getResources().getConfiguration();

        // 平板（最小宽度 ≥600dp）：不强制方向，交给系统，避免分屏/窗口变化导致闪烁
        if (cfg.smallestScreenWidthDp >= 600) {
            if (lastAppliedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
                lastAppliedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
                lastOrientationSwitchTs = now;
            }
            return;
        }

        // 手机端自适应（带滞回）
        final int widthDp = cfg.screenWidthDp;
        final int currentOri = cfg.orientation;
        final int TO_LANDSCAPE = NARROW_TO_LANDSCAPE_DP;          // 竖→横：≤600dp
        final int TO_PORTRAIT  = Math.max(WIDE_TO_PORTRAIT_DP, 800); // 横→竖：≥800dp

        int desired = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;

        if (currentOri == Configuration.ORIENTATION_PORTRAIT) {
            if (widthDp > 0 && widthDp <= TO_LANDSCAPE) {
                desired = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
            }
        } else if (currentOri == Configuration.ORIENTATION_LANDSCAPE) {
            if (widthDp >= TO_PORTRAIT) {
                desired = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
            }
        } else {
            desired = (widthDp > 0 && widthDp <= TO_LANDSCAPE)
                    ? ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
                    : ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
        }

        if (desired != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                && desired != lastAppliedOrientation) {
            setRequestedOrientation(desired);
            lastAppliedOrientation = desired;
            lastOrientationSwitchTs = now;
        }
    }

    private void renderNodesEmpty(List<ProvisionedMeshNode> nodes) {
        final boolean isEmpty = nodes == null || nodes.isEmpty();
        if (mEmptyNodesText != null) {
            mEmptyNodesText.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
        }
        if (mNodesRecyclerView != null) {
            mNodesRecyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
        }
    }

    private void renderAvailableEmpty() {
        if (mEmptyAvailableText != null && mAvailableNodesRecyclerView != null && mAvailableDevicesAdapter != null) {
            final boolean isEmpty = mAvailableDevicesAdapter.isEmpty();
            mEmptyAvailableText.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
            mAvailableNodesRecyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
        }
    }

    private void updateDeviceSpinner(List<ProvisionedMeshNode> nodes) {
        if (deviceSpinner == null || deviceSpinnerAdapter == null) return;
        deviceSpinnerItems.clear();
        spinnerIndexToNodeMap.clear();
        deviceSpinnerItems.add("选择设备");
        int index = 1;
        if (nodes != null) {
            for (ProvisionedMeshNode n : nodes) {
                deviceSpinnerItems.add(n.getNodeName());
                spinnerIndexToNodeMap.put(index, n);
                index++;
            }
        }
        deviceSpinnerAdapter.notifyDataSetChanged();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_GEAR, gear);
        outState.putInt(KEY_TIME, timeMinutes);
        outState.putBoolean(KEY_PLAY, isPlaying);
        outState.putInt(KEY_REMAIN, remainingSeconds);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // 进入前台时根据当前宽度和方向评估一次（带滞回+去抖）
        ensureOrientationWithHysteresis();

        invalidateOptionsMenu();

        // —— 只有在 BLE 就绪时才启动扫描 —— //
        if (mScannerViewModel != null) {
            if (ensureBleReady()) {
                mScannerViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
            } else {
                // 未就绪：不崩溃，保持空态
                renderAvailableEmpty();
            }
        }

        if (onlineHelper != null) onlineHelper.startTicker();

        // 回到前台也重新定位一次（以防窗口尺寸变化）
        if (ivAcupoints != null && markerHans != null) {
            placeMarkerOnImage(ivAcupoints, markerHans, DOT_RIGHT_ARM_3);
        }
        mAvailableRefreshHandler.removeCallbacks(mAvailableRefreshTick);
        mAvailableRefreshHandler.postDelayed(mAvailableRefreshTick, 2);
    }

    @Override
    protected void onPause() {
        if (onlineHelper != null) onlineHelper.stopTicker();
        if (mScannerViewModel != null) {
            mScannerViewModel.getScannerRepository().stopScan();
        }
        mAvailableRefreshHandler.removeCallbacks(mAvailableRefreshTick);
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        timerHandler.removeCallbacks(tick);
        if (mScannerViewModel != null) {
            mScannerViewModel.getScannerRepository().stopScan();
        }
        mAvailableRefreshHandler.removeCallbacks(mAvailableRefreshTick);
    }

    // ====== 顶部“用户 Chip”菜单（与医生页一致）======
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_user_chip, menu);
        bindUserChip(menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        bindUserChip(menu);
        return super.onPrepareOptionsMenu(menu);
    }

    private void bindUserChip(Menu menu) {
        MenuItem item = menu.findItem(R.id.menu_user);
        if (item == null) return;
        View actionView = item.getActionView();
        if (actionView != null) actionView.setOnClickListener(this::showUserMenu);
    }

    private void showUserMenu(View anchor) {
        PopupMenu popup = new PopupMenu(this, anchor);
        popup.getMenuInflater().inflate(R.menu.menu_user_popup, popup.getMenu());
        popup.setOnMenuItemClickListener(item -> {
            int id = item.getItemId();
            if (id == R.id.act_profile) {
                PersonalInfoDialogFragment.newInstance()
                        .show(getSupportFragmentManager(), "personal_info");
                return true;
            } else if (id == R.id.act_switch) {
                SwitchAccountDialogFragment.newInstance()
                        .show(getSupportFragmentManager(), "switch_account");
                return true;
            } else if (id == R.id.act_logout) {
                SessionManager sm = new SessionManager(UserMainActivity.this);
                sm.clearAndSkipAutoLoginOnce();
                startActivity(AuthActivity.intentFromLogout(UserMainActivity.this, /*toRegister=*/false));
                finish();
                return true;
            } else if (id == R.id.act_dev_unlock) {
                toast("开发者模式已解锁（示例）");
                return true;
            }
            return false;
        });
        popup.show();
    }

    // ====== 渲染逻辑 ======
    private void renderGear() {
        if (tvGearValue != null) {
            tvGearValue.setText(String.format("%02d", gear));
        }
    }

    private void renderTime() {
        if (tvTimeValue != null) {
            int minutes = remainingSeconds > 0 ? remainingSeconds / 60 : timeMinutes;
            int seconds = remainingSeconds > 0 ? remainingSeconds % 60 : 0;
            tvTimeValue.setText(String.format("%02d:%02d", minutes, seconds));
        }
    }

    private void renderPlay() {
        if (btnPlay != null) {
            btnPlay.setText(isPlaying ? "⏸ 停止" : "▶ 启动");
        }
    }

    private int safeParseGear(String s, int fallback) {
        try { return Integer.parseInt(s); } catch (Exception e) { return fallback; }
    }

    private int safeParseTimeToMinutes(String s, int fallback) {
        try {
            if (s == null) return fallback;
            String[] parts = s.split(":");
            return Integer.parseInt(parts[0]);
        } catch (Exception e) { return fallback; }
    }

    private void toast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    private void onBluetoothMeshPressed() {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
    }

    // —— 更新：点击可用设备，直接在本页执行无界面配网 ——
    private void onAvailableDeviceClicked(final ExtendedBluetoothDevice device) {
        if (headless != null && headless.isRunning()) {
            Toast.makeText(this, "正在配置其他节点，请稍等", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mScannerViewModel == null) {
            Toast.makeText(this, "扫描模块未就绪", Toast.LENGTH_SHORT).show();
            return;
        }
        if (headless == null) {
            headless = new MeshHeadlessProvisioner(this, this, new MeshHeadlessProvisioner.Callbacks() {
                @Override public void onProvisionStart(ExtendedBluetoothDevice d) {
                    mScannerViewModel.getScannerRepository().stopScanKeepResults();
                    setBusyUi(true, "正在配置 " + safeName(d));
                }
                @Override public void onProgressStateChanged(ProvisionerStates s) { }
                @Override public void onRequireOobInput(@androidx.annotation.NonNull MeshHeadlessProvisioner.OobRequest req) {
                    Toast.makeText(UserMainActivity.this, "设备需要 OOB 验证，请到扫描页操作", Toast.LENGTH_LONG).show();
                }
                @Override public void onFailed(@androidx.annotation.NonNull String reason) {
                    setBusyUi(false, null);
                    Toast.makeText(UserMainActivity.this, "配网失败：" + reason, Toast.LENGTH_SHORT).show();
                    if (mScannerViewModel != null) {
                        mScannerViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
                    }
                    // 继续队列中的下一个
                    if (autoProvisionActive) startNextProvisioningInQueue();
                    // 失败后若已满足数量也尝试触发下发
                    tryDispatchPendingWave();
                }
                @Override public void onCompleted(@androidx.annotation.NonNull Intent resultIntent) {
                    setBusyUi(false, null);
                    Toast.makeText(UserMainActivity.this, "配网成功", Toast.LENGTH_SHORT).show();
                    if (mScannerViewModel != null) {
                        mScannerViewModel.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
                    }
                    // 自动选中刚完成配网的节点，并继续队列
                    selectNewlyProvisionedNodeByName(currentProvisioningNodeName);
                    if (autoProvisionActive) startNextProvisioningInQueue();
                    // 完成一个节点后尝试触发下发（具体执行会在选中后二次判断）
                    tryDispatchPendingWave();
                }
                @Override public void onLog(@androidx.annotation.NonNull String line) { }
            });
        }
        final String pendingName = makeDefaultNodeName(device);
        currentProvisioningNodeName = pendingName;
        long nowTs = SystemClock.uptimeMillis();
        mPendingVisibility.put(pendingName, nowTs);
        Runnable r = new Runnable() {
            @Override public void run() {
                mPendingVisibility.remove(pendingName);
                refreshFilteredNodes();
            }
        };
        mPendingRunnables.put(pendingName, r);
        mDelayHandler.postDelayed(r, DelayTime_ms);
        ScannerLiveData scannerLiveData =
                mScannerViewModel.getScannerRepository().getScannerResults().getValue();
        if (scannerLiveData != null) {
            List<ExtendedBluetoothDevice> devices = scannerLiveData.getDevices();
            devices.remove(device);
            mAvailableDevicesAdapter.notifyDataSetChanged();
        }
        Toast.makeText(this, "正在配置节点：" + pendingName + "，请稍等", Toast.LENGTH_SHORT).show();
        final MeshHeadlessProvisioner.Options opts = MeshHeadlessProvisioner.Options.noOob(pendingName);
        headless.start(device, opts);
    }

    // —— 新增：忙碌状态（禁用点击 + 文案提示） ——
    private void setBusyUi(boolean busy, @Nullable String subtitle) {
        if (mAvailableDevicesAdapter != null) {
            mAvailableDevicesAdapter.setOnItemClickListener(busy ? null : this::onAvailableDeviceClicked);
            if (busy) {
                final String name = currentProvisioningNodeName != null ? currentProvisioningNodeName : (subtitle != null ? subtitle : "");
                mAvailableDevicesAdapter.showFooter("正在配置节点：" + name);
            } else {
                mAvailableDevicesAdapter.hideFooter();
            }
        }
        if (mEmptyAvailableText != null) {
            if (busy) {
                mEmptyAvailableText.setText(subtitle != null ? subtitle : "正在执行配网…");
                mEmptyAvailableText.setVisibility(View.VISIBLE);
            } else {
                if (mEmptyAvailableDefaultText != null) {
                    mEmptyAvailableText.setText(mEmptyAvailableDefaultText);
                }
                renderAvailableEmpty();
            }
        }
    }

    // —— 新增：工具方法 ——
    private String safeName(final ExtendedBluetoothDevice d) {
        final String n = d != null ? d.getName() : null;
        return n != null ? n : "Unknown";
    }
    private String makeDefaultNodeName(final ExtendedBluetoothDevice d) {
        final String name = d != null && d.getName() != null ? d.getName() : "Node";
        final String addrTail = d != null && d.getAddress() != null
                ? d.getAddress().replace(":", "")
                : String.valueOf(System.currentTimeMillis() % 10000);
        return name + "-" + addrTail.substring(Math.max(0, addrTail.length() - 4));
    }

    // ====== 模式触发的自动配网逻辑 ======
    private void startAutoProvisionForMode(final int count) {
        if (mScannerViewModel == null) { toast("扫描模块未就绪"); return; }
        final ScannerLiveData live = mScannerViewModel.getScannerRepository().getScannerResults().getValue();
        final List<ExtendedBluetoothDevice> devices = (live != null) ? new ArrayList<>(live.getDevices()) : new ArrayList<>();
        if (devices.isEmpty()) { toast("暂无可用设备"); return; }
        final int need = Math.min(count, devices.size());
        provisionQueue.clear();
        autoProvisionActive = true;
        for (int i = 0; i < need; i++) {
            provisionQueue.add(devices.get(i));
        }
        startNextProvisioningInQueue();
    }

    private void startNextProvisioningInQueue() {
        if (headless != null && headless.isRunning()) return;
        final ExtendedBluetoothDevice next = provisionQueue.poll();
        if (next == null) { autoProvisionActive = false; tryDispatchPendingWave(); return; }
        onAvailableDeviceClicked(next);
    }

    private void selectNewlyProvisionedNodeByName(@androidx.annotation.Nullable final String nodeName) {
        if (nodeName == null || mSharedViewModel == null) return;
        // 延迟一点等待节点列表刷新
        mDelayHandler.postDelayed(() -> {
            final List<ProvisionedMeshNode> nodes = mSharedViewModel.getNodes().getValue();
            if (nodes == null) return;
            for (ProvisionedMeshNode n : nodes) {
                if (n != null && nodeName.equals(n.getNodeName())) {
                    selectNode(n);
                    // 首次分配颜色
                    final String key = String.valueOf(n.getUnicastAddress());
                    if (!firstClickAssignedColor.containsKey(key)) {
                        firstClickAssignedColor.put(key, nextColorToAssign);
                        nextColorToAssign = nextColorToAssign % 6 + 1;
                        if (mNodeAdapter != null) mNodeAdapter.setAssignedColors(firstClickAssignedColor);
                        final List<ProvisionedMeshNode> all = mSharedViewModel.getNodes().getValue();
                        updateDeviceSpinner(all);
                    }
                    // 新增节点选中时延长等待窗口，确保自动下发不会因超时取消
                    if (waveDispatchRequested) {
                        waveRetryDeadlineUptime = android.os.SystemClock.uptimeMillis() + WaveRetryMaxMs;
                    }
                    break;
                }
            }
            // 节点选中与配置完成后，尝试触发挂起的波形下发
            tryDispatchPendingWave();
        }, DelayTime_ms);
    }

    // —— 新增：尝试触发挂起的波形下发（满足数量后自动发送一次） ——
    private void tryDispatchPendingWave() {
        if (!waveDispatchRequested) return;
        final int required;
        if (pendingWaveType > 0) {
            required = pendingWaveType;
        } else if (currentMode == 2) {
            required = 2;
        } else if (currentMode == 1) {
            required = 1;
        } else if (currentMode == 3) {
            required = 3;
        } else {
            return;
        }
        // 在等待就绪期间不移除离线节点，以免选中数量被误减
        if (selectedNodes.size() >= required) {
            for (int i = 0; i < required; i++) {
                ProvisionedMeshNode node = selectedNodes.get(i);
                autoConfigureNode(node);
            }
            boolean allReady = true;
            for (int i = 0; i < required; i++) {
                ProvisionedMeshNode node = selectedNodes.get(i);
                if (!isVendorModelReady(node)) { allReady = false; break; }
            }
            if (allReady) {
                sendColorJsonToSelectedNodes(required);
                mDelayHandler.postDelayed(() -> sendWaveJsonToSelectedNodes(required), 20);
                waveDispatchRequested = false;
                pendingWaveType = 0;
                waveRetryDeadlineUptime = 0;
            } else {
                if (waveRetryDeadlineUptime > 0 && android.os.SystemClock.uptimeMillis() < waveRetryDeadlineUptime) {
                    mDelayHandler.postDelayed(this::tryDispatchPendingWave, WaveRetryIntervalMs);
                } else {
                    waveDispatchRequested = false;
                    pendingWaveType = 0;
                    waveRetryDeadlineUptime = 0;
                    toast("设备未就绪，已取消本次自动下发");
                }
            }
        } else {
            // 始终等待选中数量达到要求，避免过早取消
            if (waveRetryDeadlineUptime == 0) {
                waveRetryDeadlineUptime = android.os.SystemClock.uptimeMillis() + WaveRetryMaxMs;
            }
            mDelayHandler.postDelayed(this::tryDispatchPendingWave, WaveRetryIntervalMs);
        }
    }

    private boolean isVendorModelReady(@androidx.annotation.NonNull final ProvisionedMeshNode node) {
        try {
            final Map<Integer, Element> elements = node.getElements();
            if (elements == null) return false;
            for (Element e : elements.values()) {
                for (no.nordicsemi.android.mesh.transport.MeshModel m : e.getMeshModels().values()) {
                    if (m instanceof no.nordicsemi.android.mesh.models.VendorModel) {
                        final no.nordicsemi.android.mesh.models.VendorModel vm = (no.nordicsemi.android.mesh.models.VendorModel) m;
                        final java.util.List<Integer> bound = vm.getBoundAppKeyIndexes();
                        if (bound != null && !bound.isEmpty()) return true;
                    }
                }
            }
        } catch (Exception ignored) {}
        return false;
    }

    // ====== 启动按钮：按模式下发波形 JSON ======
    private void sendWaveJsonToSelectedNodes(final int waveType) {
        try {
            final List<ProvisionedMeshNode> targets = new ArrayList<>();
            for (int i = 0; i < selectedNodes.size() && i < waveType; i++) {
                final ProvisionedMeshNode n = selectedNodes.get(i);
                if (n != null) targets.add(n);
            }
            if (targets.isEmpty()) { toast("请先选中已配网节点"); return; }

            final String json;
            if (waveType == 2) {
                json = "{" + "\"treatment mode\":" + 2 + "}";
            } else if (waveType == 3) {
                json = "{" + "\"treatment mode\":" + 3 + "}";
            } else {
                json ="{" + "\"treatment mode\":" + 1 + "}";
            }
            final byte[] payload = json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
            final int opcode = getVendorOpCode();

            int sent = 0;
            for (ProvisionedMeshNode node : targets) {
                if (node == null) continue;
                final boolean ok = sendVendorModelMessageToNode(node, opcode, payload);
                if (ok) sent++;
            }
        } catch (Exception e) {
            toast("下发失败：" + e.getMessage());
        }
    }

    private int getVendorOpCode() {
        SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
        String op = prefs.getString("opCode", "0xC0");
        try {
            if (op != null) {
                return Integer.decode(op);
            }
        } catch (Exception ignored) {}
        return 0xC0;
    }

    // 批量向选中节点下发颜色 JSON（模式一）
    private void sendColorJsonToSelectedNodes(final int count) {
        try {
            final List<ProvisionedMeshNode> targets = new ArrayList<>();
            for (int i = 0; i < selectedNodes.size() && i < count; i++) {
                final ProvisionedMeshNode n = selectedNodes.get(i);
                if (n != null) targets.add(n);
            }
            if (targets.isEmpty()) { toast("请先选中已配网节点"); return; }

            final int opcode = getVendorOpCode();
            int sent = 0;
            for (ProvisionedMeshNode node : targets) {
                final String key = String.valueOf(node.getUnicastAddress());
                final Integer color = firstClickAssignedColor.get(key);
                final int colorValue = color != null ? color : 1;
                final String json = "{" +
                        "\"color\":" + colorValue + "}";
                final byte[] payload = json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
                final boolean ok = sendVendorModelMessageToNode(node, opcode, payload);
                if (ok) sent++;
            }
        } catch (Exception e) {
            toast("颜色下发失败：" + e.getMessage());
        }
    }

    // 颜色分配 JSON 下发（带重试）
    private void sendColorJsonForNodeWithRetry(@androidx.annotation.NonNull final ProvisionedMeshNode node, final int color, final int attempt) {
        final String json = "{" +
                "\"color\":" + color + "}";
        final byte[] payload = json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
        final int opcode = getVendorOpCode();
        final boolean ok = sendVendorModelMessageToNode(node, opcode, payload);
        if (!ok && attempt < 16) {
            mDelayHandler.postDelayed(() -> sendColorJsonForNodeWithRetry(node, color, attempt + 1), 500);
        }
    }

    private boolean sendVendorModelMessageToNode(@androidx.annotation.NonNull final ProvisionedMeshNode node, final int opcode, @androidx.annotation.NonNull final byte[] parameters) {
        try {
            // 确保已绑定 AppKey 与 Vendor 模型
            autoConfigureNode(node);

            final MeshNetwork network = mSharedViewModel.getNetworkLiveData().getMeshNetwork();
            if (network == null) return false;

            Element chosenElement = null;
            VendorModel chosenModel = null;
            final Map<Integer, Element> elements = node.getElements();
            if (elements != null) {
                for (Element e : elements.values()) {
                    for (MeshModel m : e.getMeshModels().values()) {
                        if (m instanceof VendorModel) {
                            final VendorModel vm = (VendorModel) m;
                            final List<Integer> bound = vm.getBoundAppKeyIndexes();
                            if (bound != null && !bound.isEmpty()) {
                                chosenElement = e;
                                chosenModel = vm;
                                break;
                            }
                        }
                    }
                    if (chosenModel != null) break;
                }
            }
            if (chosenElement == null || chosenModel == null) {
                return false;
            }

            final int appKeyIndex = chosenModel.getBoundAppKeyIndexes().get(0);
            final ApplicationKey appKey = network.getAppKey(appKeyIndex);
            if (appKey == null) return false;

            final MeshMessage msg = new VendorModelMessageAcked(appKey, chosenModel.getModelId(), chosenModel.getCompanyIdentifier(), opcode, parameters);
            mSharedViewModel.getMeshManagerApi().createMeshPdu(node.getUnicastAddress(), msg);
            // 更新 ViewModel 的选中上下文（便于后续操作）
            mSharedViewModel.setSelectedMeshNode(node);
            mSharedViewModel.setSelectedElement(chosenElement);
            mSharedViewModel.setSelectedModel(chosenModel);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    // —— 新增：节点点击处理 ——
    private void handleNodeClick(final ProvisionedMeshNode node) {
        if (node == null) return;
        if (mNodeAdapter != null) {
            mNodeAdapter.setSelectedNode(node);
        }
        // 与主界面一致：处理多选/阻塞逻辑
        handleNodeSelection(node);

        final String key = String.valueOf(node.getUnicastAddress());
        if (!firstClickAssignedColor.containsKey(key)) {
            firstClickAssignedColor.put(key, nextColorToAssign);
            nextColorToAssign = nextColorToAssign % 6 + 1; // 循环颜色 1..6（首色粉色）
            if (mNodeAdapter != null) {
                mNodeAdapter.setAssignedColors(firstClickAssignedColor);
            }
            // 刷新设备下拉颜色
            List<ProvisionedMeshNode> nodes = mSharedViewModel != null ? mSharedViewModel.getNodes().getValue() : null;
            updateDeviceSpinner(nodes);
        }
        if (mSharedViewModel != null) {
            mSharedViewModel.setSelectedMeshNode(node);
        }
    }

    // —— 新增：颜色映射 ——
    private int colorIntFor(final int color) {
        switch (color) {
            case 1: return Color.parseColor("#FFC0CB"); // 粉色
            case 2: return Color.GREEN;   // 绿色
            case 3: return Color.YELLOW;  // 黄色
            case 4: return Color.MAGENTA; // 紫色
            case 5: return Color.CYAN;    // 青色
            case 6: return Color.parseColor("#FFA500"); // 橙色
            default: return Color.parseColor("#4D79FF"); // 默认蓝
        }
    }

    // —— 过滤与刷新辅助方法 ——
    private List<ProvisionedMeshNode> applyProvisionedFilter(List<ProvisionedMeshNode> nodes) {
        return onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms);
    }

    private void refreshFilteredNodes() {
        List<ProvisionedMeshNode> nodes = mSharedViewModel.getNodes().getValue();
        if (nodes != null) {
            List<ProvisionedMeshNode> filtered = onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms);
            onlineHelper.setFilteredNodesWithOfflineToast(mFilteredNodes, filtered);
        }
    }

    // —— 节点选择管理 ——
    private void handleNodeSelection(ProvisionedMeshNode node) {
        boolean isSelected = false;
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && sn.getUnicastAddress() == node.getUnicastAddress()) {
                isSelected = true;
                break;
            }
        }
        if (isSelected) {
            deselectNode(node);
        } else {
            selectNode(node);
        }
        updateNodeAdapterSelection();
    }

    private void selectNode(ProvisionedMeshNode node) {
        addNodeToSelection(node);
        if (mSharedViewModel != null) {
            mSharedViewModel.setSelectedMeshNode(node);
        }
        // 自动配置 AppKey 与 Vendor 模型
        autoConfigureNode(node);
        ProvisionedMeshNode toRemove = null;
        for (ProvisionedMeshNode bn : blockedNodes) {
            if (bn != null && bn.getUnicastAddress() == node.getUnicastAddress()) {
                toRemove = bn;
                break;
            }
        }
        if (toRemove != null) blockedNodes.remove(toRemove);
        handleDataBlocking();
    }

    private void deselectNode(ProvisionedMeshNode node) {
        removeNodeFromSelection(node);
        if (mSharedViewModel != null && mSharedViewModel.getSelectedMeshNode().getValue() != null &&
                mSharedViewModel.getSelectedMeshNode().getValue().getUnicastAddress() == node.getUnicastAddress()) {
            mSharedViewModel.clearSelectedMeshNode();
        }
        handleDataBlocking();
    }

    private void updateNodeAdapterSelection() {
        if (mNodeAdapter != null) {
            mNodeAdapter.setSelectedNodes(selectedNodes);
            mNodeAdapter.setBlockedNodes(blockedNodes);
            if (mNodesRecyclerView != null) {
                mNodesRecyclerView.post(() -> {
                    mNodesRecyclerView.requestLayout();
                    mNodesRecyclerView.invalidate();
                });
            }
        }
    }

    // 将所有离线节点从选中集合中移除，并清理 ViewModel 的选中状态（与主界面一致）
    private void pruneSelectionForOfflineNodes() {
        if (selectedNodes.isEmpty()) return;
        List<ProvisionedMeshNode> toRemove = new ArrayList<>();
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && (onlineHelper == null || !onlineHelper.isOnline(sn.getUnicastAddress()))) {
                toRemove.add(sn);
            }
        }
        for (ProvisionedMeshNode rm : toRemove) {
            removeNodeFromSelection(rm);
        }
        final ProvisionedMeshNode sel = mSharedViewModel.getSelectedMeshNode().getValue();
        if (sel != null && (onlineHelper == null || !onlineHelper.isOnline(sel.getUnicastAddress()))) {
            mSharedViewModel.clearSelectedMeshNode();
        }
        updateNodeAdapterSelection();
    }

    private void addNodeToSelection(ProvisionedMeshNode node) {
        boolean alreadySelected = false;
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && sn.getUnicastAddress() == node.getUnicastAddress()) {
                alreadySelected = true;
                break;
            }
        }
        if (!alreadySelected) {
            selectedNodes.add(node);
            List<ProvisionedMeshNode> nodes = mSharedViewModel != null ? mSharedViewModel.getNodes().getValue() : null;
            updateDeviceSpinner(nodes);
        }
    }

    private void removeNodeFromSelection(ProvisionedMeshNode node) {
        ProvisionedMeshNode toRemove = null;
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && sn.getUnicastAddress() == node.getUnicastAddress()) {
                toRemove = sn;
                break;
            }
        }
        if (toRemove != null) {
            selectedNodes.remove(toRemove);
            List<ProvisionedMeshNode> nodes = mSharedViewModel != null ? mSharedViewModel.getNodes().getValue() : null;
            updateDeviceSpinner(nodes);
        }
    }

    // —— 自动配置：AppKey 与 Vendor 模型 ——
    private void autoConfigureNode(ProvisionedMeshNode node) {
        if (node == null || mMeshAutoConfigurator == null || mSharedViewModel == null) return;
        try {
            mMeshAutoConfigurator.initDefaultAppKey(mSharedViewModel.getMeshManagerApi());
            mMeshAutoConfigurator.autoBindAllVendorModels(node, mSharedViewModel.getMeshManagerApi());
            setOpCodeForVendorModels("0xC0");
            if (!node.isConfigured()) {
                node.setConfigured(true);
            }
        } catch (Exception e) {
            Toast.makeText(this, "AppKey自动配置失败: " + e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    private void setOpCodeForVendorModels(String opCode) {
        SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString("opCode", opCode);
        editor.apply();
    }

    private void handleDataBlocking() {
        List<ProvisionedMeshNode> allNodes = mSharedViewModel != null && mSharedViewModel.getNetworkLiveData().getMeshNetwork() != null
                ? mSharedViewModel.getNetworkLiveData().getMeshNetwork().getNodes()
                : new ArrayList<>();
        Set<Integer> selectedAddrs = new HashSet<>();
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null) selectedAddrs.add(sn.getUnicastAddress());
        }
        List<ProvisionedMeshNode> toUnblock = new ArrayList<>();
        for (ProvisionedMeshNode bn : blockedNodes) {
            if (bn != null && selectedAddrs.contains(bn.getUnicastAddress())) {
                toUnblock.add(bn);
            }
        }
        for (ProvisionedMeshNode bn : toUnblock) blockedNodes.remove(bn);
        for (ProvisionedMeshNode node : allNodes) {
            if (node == null) continue;
            int addr = node.getUnicastAddress();
            if (!selectedAddrs.contains(addr)) {
                boolean exists = false;
                for (ProvisionedMeshNode bn : blockedNodes) {
                    if (bn != null && bn.getUnicastAddress() == addr) {
                        exists = true;
                        break;
                    }
                }
                if (!exists) blockedNodes.add(node);
            }
        }
    }

    // —— 更新：彩色设备下拉 ——
    private class ColoredDeviceAdapter extends android.widget.ArrayAdapter<String> {
        ColoredDeviceAdapter(int resource, List<String> items) {
            super(UserMainActivity.this, resource, items);
            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        }
        @Override
        public View getView(int position, View convertView, android.view.ViewGroup parent) {
            View v = super.getView(position, convertView, parent);
            applyColorToView(v, position);
            return v;
        }
        @Override
        public View getDropDownView(int position, View convertView, android.view.ViewGroup parent) {
            View v = super.getDropDownView(position, convertView, parent);
            applyColorToView(v, position);
            return v;
        }
        private void applyColorToView(View v, int position) {
            TextView tv = v instanceof TextView ? (TextView) v : v.findViewById(android.R.id.text1);
            if (tv == null) return;
            if (position == 0) {
                tv.setTextColor(Color.GRAY);
                return;
            }
            ProvisionedMeshNode node = spinnerIndexToNodeMap.get(position);
            if (node != null) {
                Integer c = firstClickAssignedColor.get(String.valueOf(node.getUnicastAddress()));
                if (c != null) {
                    tv.setTextColor(colorIntFor(c));
                    return;
                }
            }
            tv.setTextColor(Color.BLACK);
        }
    }

    // ======= 新增：把归一化坐标落到屏幕并移动 marker =======
    private void placeMarkerOnImage(ImageView iv, View marker, PointF norm) {
        if (iv == null || marker == null || iv.getDrawable() == null) return;

        // 取出 ImageMatrix 的缩放和平移
        float[] vals = new float[9];
        Matrix m = iv.getImageMatrix();
        m.getValues(vals);
        float scaleX = vals[Matrix.MSCALE_X];
        float scaleY = vals[Matrix.MSCALE_Y];
        float transX = vals[Matrix.MTRANS_X];
        float transY = vals[Matrix.MTRANS_Y];

        int dw = iv.getDrawable().getIntrinsicWidth();
        int dh = iv.getDrawable().getIntrinsicHeight();

        // 映射到 ImageView 的内容像素坐标
        float imgX = transX + norm.x * dw * scaleX;
        float imgY = transY + norm.y * dh * scaleY;

        // 把 marker 的中心对齐到目标点
        float targetX = iv.getLeft() + imgX - marker.getWidth() / 2f;
        float targetY = iv.getTop()  + imgY - marker.getHeight() / 2f;

        View parent = (View) marker.getParent();
        if (parent == null) return;
        marker.setX(targetX - parent.getLeft());
        marker.setY(targetY - parent.getTop());
    }

    // ======= 新增：根据所选设备的“标识色”给标记点着色 =======
    private void updateMarkerColorForSelectedNode(@Nullable ProvisionedMeshNode node) {
        if (markerHans == null) return;
        int argb = Color.parseColor("#4D79FF"); // 默认蓝
        if (node != null) {
            Integer idx = firstClickAssignedColor.get(String.valueOf(node.getUnicastAddress()));
            if (idx != null) argb = colorIntFor(idx);
        }
        Drawable bg = markerHans.getBackground();
        if (bg != null) {
            bg = bg.mutate();
            try {
                // ripple/shape 都支持 setTint
                bg.setTint(argb);
            } catch (Throwable ignore) {
                // 少数老设备不支持时兜底：直接设置背景色
                markerHans.setBackgroundColor(argb);
            }
            markerHans.setBackground(bg);
        } else {
            markerHans.setBackgroundColor(argb);
        }
    }
}
